/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package org.latestbit.smartobjects.network;

import java.io.IOException;
import java.net.URI;
import java.util.*;

import org.latestbit.smartobjects.exceptions.IncorrectURIException;
import org.latestbit.smartobjects.utils.PostActionListenersContainer;


public class ManagedServer implements IManagedServer, IManagedConnectionFactory, IAcceptorListener {

	private Collection<IManagedServerListener> listeners = new LinkedList<IManagedServerListener>();
	protected PostActionListenersContainer<IManagedServerListener> palServerListeners = new PostActionListenersContainer<IManagedServerListener> ();
	
	private IEndpointLocation reference;
	private Map<String, IAcceptor> acceptors = new HashMap < String, IAcceptor >();
	private Set<ConnectionHandler> allHandlers = new HashSet < ConnectionHandler> ();
	private Set<IManagedConnection> connections = new HashSet < IManagedConnection >();
	
	private INetworkFactory factory;
	private boolean started = false;
	private IEndpointCoordinator endpointCoordinator;
		
	public ManagedServer(INetworkFactory factory) {
		this.factory = factory;
	}
	
	@Override
	public void addListener(IManagedServerListener listener) {
		synchronized(listeners) {
			if(!this.palServerListeners.isActivated()) {
				listeners.add(listener);
			}
			else
				this.palServerListeners.putListenerToAdd(listener);
		}
	}

	@Override
	public void delListener(IManagedServerListener listener) {
		synchronized(listeners) {
			if(!this.palServerListeners.isActivated()) {
				listeners.remove(listener);
			}
			else
				this.palServerListeners.putListenerToRemove(listener);
		}
	}

	@Override
	public IEndpointLocation getEndpointLocation() {
		return reference;
	}

	@Override
	public void setEndpointLocation(IEndpointLocation reference, IEndpointCoordinator coordinator)
			throws IncorrectURIException, IOException {
		synchronized(acceptors ) {
			if(this.endpointCoordinator!=null) {
				this.endpointCoordinator.setManagedConnectionFactory(null);
			}
			this.reference = reference;
			this.endpointCoordinator = coordinator;
			if(this.endpointCoordinator!=null) {
				this.endpointCoordinator.setManagedConnectionFactory(this);
			}
			updateAcceptors();
		}
	}

	protected void updateAcceptors() throws IncorrectURIException, IOException {
		synchronized(acceptors) {
			Map<String, IAcceptor> acceptorsTemp = acceptors;
			acceptors.clear();
			for ( URI location : getEndpointLocation().getLocations() ) {
				String key = createKey ( location );
				IAcceptor existAcceptor =  acceptorsTemp.get(key);
				if(existAcceptor!=null) {
					// Only coping an exist acceptor to storage
					acceptors.put(key, existAcceptor);
				}
				else {
					// Creating a new acceptor
					IAcceptor acceptor = factory.createAcceptor();
					acceptor.setEndpointURI(location);
					acceptor.addListener(this);

					acceptors.put(key, acceptor);
					if( started )
						acceptor.start();
				}
			}
		}		
	}

	public String createKey(URI location) {
		return location.getHost()+":"+location.getPort();
	}
	
	@Override
	public void onAccepted(IConnection connection) {
		synchronized(acceptors) {
			if(!started)
				return;
			ConnectionHandler handler = new ConnectionHandler (
					connection,
					endpointCoordinator
			);
			allHandlers.add(handler);
			connection.addConnectionListener(handler);
			connection.addStreamListener(handler);
			fireOnConnected(handler);
			handler.onConnected(connection);
		}
	}	

	protected void fireOnConnected(ConnectionHandler handler) {
		synchronized(listeners) {
			palServerListeners.activate();
			try {			
				for(IManagedServerListener listener : listeners) {
					listener.onConnected(handler);
				}
			}
			finally {
				processingPostActionServerListeners();
			}			
		}
	}

	protected void fireOnConnected(IManagedConnection connection) {
		synchronized(listeners) {
			palServerListeners.activate();
			try {						
				for(IManagedServerListener listener : listeners) {
					listener.onConnected(connection);
				}
			}
			finally {
				processingPostActionServerListeners();
			}				
		}
	}
	
	protected void processingPostActionServerListeners() {
		palServerListeners.deactivate();
		try {
			for ( PostActionListenersContainer<IManagedServerListener>.PostActionQueueItem item : palServerListeners.getPostActionQueue() ) {
				if( item.isActionAdd() ) {
					addListener ( item.getListener() );
				}
				else
				if( item.isActionRemove() ) {
					delListener ( item.getListener() );
				}
			}
		}
		finally {
			palServerListeners.reset();
		}	
	}	
	
	@Override
	public synchronized void start() throws IncorrectURIException, IOException {
		synchronized(acceptors) {
			for ( IAcceptor acceptor : acceptors.values() ) {
				acceptor.start();
			}
			started = true;
		}
	}

	@Override
	public synchronized void stop() {
		synchronized(acceptors) {
			started = false;
		}
		Set<IManagedConnection> connectionsTemp = new HashSet<IManagedConnection>();
		synchronized(connections) {
			connectionsTemp.addAll(connections);
			connections.clear();
		}
		synchronized(acceptors) {
			
			for(IAcceptor acceptor : acceptors.values()) {
				acceptor.stop();
			}
			
			for(ConnectionHandler handler : allHandlers) {
				handler.getConnection().delConnectionListener(handler);
				handler.getConnection().delStreamListener(handler);
				endpointCoordinator.onRemoved(handler);
			}
			allHandlers.clear();


		}
	}
	
	@Override
	public void finalize() {
		destroy();
	}
	
	@Override
	public void destroy() {
		stop();
		for(IAcceptor acceptor : acceptors.values()) {
			acceptor.destroy();
		}		
	}

	@Override
	public IManagedConnection createManagedConnection() {
		synchronized(connections) {
			IManagedConnection connection = new ManagedServerConnection(this, endpointCoordinator);
			connections.add(connection);
			return connection;
		}		
	}

	@Override
	public void removeManagedConnection(IManagedConnection connection) {
		synchronized(connections) {
			connections.remove(connection);
		}
	}
	
	protected void removeHandler(ConnectionHandler handler) {
		synchronized(acceptors) {
			allHandlers.remove(handler);
			endpointCoordinator.onRemoved(handler);
		}
	}

}
